/* ---------------------------------------------------------------------------- */
/*                  Atmel Microcontroller Software Support                      */
/*                       SAM Software Package License                           */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation                                        */
/*                                                                              */
/* All rights reserved.                                                         */
/*                                                                              */
/* Redistribution and use in source and binary forms, with or without           */
/* modification, are permitted provided that the following condition is met:    */
/*                                                                              */
/* - Redistributions of source code must retain the above copyright notice,     */
/* this list of conditions and the disclaimer below.                            */
/*                                                                              */
/* Atmel's name may not be used to endorse or promote products derived from     */
/* this software without specific prior written permission.                     */
/*                                                                              */
/* DISCLAIMER:  THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR   */
/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE   */
/* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,      */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,  */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    */
/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING         */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                           */
/* ---------------------------------------------------------------------------- */

/** \file */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/
#include "chip.h"

/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Configures one or more pin(s) of a PIO controller as being controlled by
 * peripheral A. Optionally, the corresponding internal pull-up(s) can be enabled.
 *
 * \param pio  Pointer to a PIO controller.
 * \param mask  Bitmask of one or more pin(s) to configure.
 * \param enablePullUp  Indicates if the pin(s) internal pull-up shall be
 *                      configured.
 */
static void PIO_SetPeripheralA(
	Pio *pio,
	unsigned int mask,
	unsigned char enablePullUp)
{
	unsigned int abcdsr;
	/* Disable interrupts on the pin(s) */
	pio->PIO_IDR = mask;

	/* Enable the pull-up(s) if necessary */
	if (enablePullUp)
		pio->PIO_PUER = mask;
	else
		pio->PIO_PUDR = mask;

	abcdsr = pio->PIO_ABCDSR[0];
	pio->PIO_ABCDSR[0] &= (~mask & abcdsr);
	abcdsr = pio->PIO_ABCDSR[1];
	pio->PIO_ABCDSR[1] &= (~mask & abcdsr);
	pio->PIO_PDR = mask;
}

/**
 * \brief Configures one or more pin(s) of a PIO controller as being controlled
 * by peripheral B. Optionally, the corresponding internal pull-up(s) can be
 * enabled.
 *
 * \param pio  Pointer to a PIO controller.
 * \param mask  Bitmask of one or more pin(s) to configure.
 * \param enablePullUp  Indicates if the pin(s) internal pull-up shall be
 *                      configured.
 */
static void PIO_SetPeripheralB(
	Pio *pio,
	unsigned int mask,
	unsigned char enablePullUp)
{
	unsigned int abcdsr;
	/* Disable interrupts on the pin(s) */
	pio->PIO_IDR = mask;

	/* Enable the pull-up(s) if necessary */
	if (enablePullUp)
		pio->PIO_PUER = mask;
	else
		pio->PIO_PUDR = mask;

	abcdsr = pio->PIO_ABCDSR[0];
	pio->PIO_ABCDSR[0] = (mask | abcdsr);
	abcdsr = pio->PIO_ABCDSR[1];
	pio->PIO_ABCDSR[1] &= (~mask & abcdsr);

	pio->PIO_PDR = mask;
}

/**
 * \brief Configures one or more pin(s) of a PIO controller as being controlled
 * by peripheral C. Optionally, the corresponding internal pull-up(s) can be
 * enabled.
 *
 * \param pio  Pointer to a PIO controller.
 * \param mask  Bitmask of one or more pin(s) to configure.
 * \param enablePullUp  Indicates if the pin(s) internal pull-up shall be
 *                      configured.
 */
static void PIO_SetPeripheralC(
	Pio *pio,
	unsigned int mask,
	unsigned char enablePullUp)
{
	unsigned int abcdsr;
	/* Disable interrupts on the pin(s) */
	pio->PIO_IDR = mask;

	/* Enable the pull-up(s) if necessary */
	if (enablePullUp)
		pio->PIO_PUER = mask;
	else
		pio->PIO_PUDR = mask;

	abcdsr = pio->PIO_ABCDSR[0];
	pio->PIO_ABCDSR[0] &= (~mask & abcdsr);
	abcdsr = pio->PIO_ABCDSR[1];
	pio->PIO_ABCDSR[1] = (mask | abcdsr);

	pio->PIO_PDR = mask;
}

/**
 * \brief Configures one or more pin(s) of a PIO controller as being controlled
 * by peripheral D. Optionally, the corresponding internal pull-up(s) can be
 * enabled.
 *
 * \param pio  Pointer to a PIO controller.
 * \param mask  Bitmask of one or more pin(s) to configure.
 * \param enablePullUp  Indicates if the pin(s) internal pull-up shall be
 *                      configured.
 */
static void PIO_SetPeripheralD(
	Pio *pio,
	unsigned int mask,
	unsigned char enablePullUp)
{
	unsigned int abcdsr;
	/* Disable interrupts on the pin(s) */
	pio->PIO_IDR = mask;

	/* Enable the pull-up(s) if necessary */
	if (enablePullUp)
		pio->PIO_PUER = mask;
	else
		pio->PIO_PUDR = mask;

	abcdsr = pio->PIO_ABCDSR[0];
	pio->PIO_ABCDSR[0] = (mask | abcdsr);
	abcdsr = pio->PIO_ABCDSR[1];
	pio->PIO_ABCDSR[1] = (mask | abcdsr);

	pio->PIO_PDR = mask;
}

/**
 * \brief Configures one or more pin(s) or a PIO controller as inputs. Optionally,
 * the corresponding internal pull-up(s) and glitch filter(s) can be enabled.
 *
 * \param pio  Pointer to a PIO controller.
 * \param mask  Bitmask indicating which pin(s) to configure as input(s).
 * \param enablePullUp  Indicates if the internal pull-up(s) must be enabled.
 * \param enableFilter  Indicates if the glitch filter(s) must be enabled.
 */
static void PIO_SetInput(
	Pio *pio,
	unsigned int mask,
	unsigned char attribute)
{
	/* Disable interrupts */
	pio->PIO_IDR = mask;

	/* Enable pull-up(s) if necessary */
	if (attribute & PIO_PULLUP)
		pio->PIO_PUER = mask;
	else
		pio->PIO_PUDR = mask;

	/* Enable Input Filter if necessary */
	if (attribute & (PIO_DEGLITCH | PIO_DEBOUNCE))
		pio->PIO_IFER = mask;
	else
		pio->PIO_IFDR = mask;

	/* Enable de-glitch or de-bounce if necessary */
	if (attribute & PIO_DEGLITCH)
		pio->PIO_IFSCDR = mask;
	else {
		if (attribute & PIO_DEBOUNCE)
			pio->PIO_IFSCER = mask;
	}

	/* Configure additional interrupt mode registers */
	if (attribute & PIO_IT_AIME) {
		// enable additional interrupt mode
		pio->PIO_AIMER  = mask;

		// if bit field of selected pin is 1, set as Rising Edge/High level detection event
		if (attribute & PIO_IT_RE_OR_HL)
			pio->PIO_REHLSR = mask;
		else
			pio->PIO_FELLSR = mask;

		/* if bit field of selected pin is 1, set as edge detection source */
		if (attribute & PIO_IT_EDGE)
			pio->PIO_ESR = mask;
		else
			pio->PIO_LSR = mask;
	} else {
		/* disable additional interrupt mode */
		pio->PIO_AIMDR = mask;
	}

	/* Configure pin as input */
	pio->PIO_ODR = mask;
	pio->PIO_PER = mask;
}

/**
 * \brief Configures one or more pin(s) of a PIO controller as outputs, with the
 * given default value. Optionally, the multi-drive feature can be enabled
 * on the pin(s).
 *
 * \param pio  Pointer to a PIO controller.
 * \param mask  Bitmask indicating which pin(s) to configure.
 * \param defaultValue  Default level on the pin(s).
 * \param enableMultiDrive  Indicates if the pin(s) shall be configured as
 *                          open-drain.
 * \param enablePullUp  Indicates if the pin shall have its pull-up activated.
 */
static void PIO_SetOutput(
	Pio *pio,
	unsigned int mask,
	unsigned char defaultValue,
	unsigned char enableMultiDrive,
	unsigned char enablePullUp)
{
	/* Disable interrupts */
	pio->PIO_IDR = mask;

	/* Enable pull-up(s) if necessary */
	if (enablePullUp)
		pio->PIO_PUER = mask;
	else
		pio->PIO_PUDR = mask;

	/* Enable multi-drive if necessary */
	if (enableMultiDrive)
		pio->PIO_MDER = mask;
	else
		pio->PIO_MDDR = mask;

	/* Set default value */
	if (defaultValue)
		pio->PIO_SODR = mask;
	else
		pio->PIO_CODR = mask;

	/* Configure pin(s) as output(s) */
	pio->PIO_OER = mask;
	pio->PIO_PER = mask;
}

/*----------------------------------------------------------------------------
 *         Global functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Configures a list of Pin instances, each of which can either hold a
 * single pin or a group of pins, depending on the mask value; all pins are
 * configured by this function. The size of the array must also be provided and
 *  is easily computed using PIO_LISTSIZE whenever its length is not known in
 * advance.
 *
 * \param list  Pointer to a list of Pin instances.
 * \param size  Size of the Pin list (calculated using PIO_LISTSIZE).
 *
 * \return 1 if the pins have been configured properly; otherwise 0.
 */
uint8_t PIO_Configure(const Pin *list, uint32_t size)
{
	/* Configure pins */
	while (size > 0) {
		switch (list->type) {
		case PIO_PERIPH_A:
			PIO_SetPeripheralA(list->pio,
							   list->mask,
							   (list->attribute & PIO_PULLUP) ? 1 : 0);
			break;

		case PIO_PERIPH_B:
			PIO_SetPeripheralB(list->pio,
							   list->mask,
							   (list->attribute & PIO_PULLUP) ? 1 : 0);
			break;

		case PIO_PERIPH_C:
			PIO_SetPeripheralC(list->pio,
							   list->mask,
							   (list->attribute & PIO_PULLUP) ? 1 : 0);
			break;

		case PIO_PERIPH_D:
			PIO_SetPeripheralD(list->pio,
							   list->mask,
							   (list->attribute & PIO_PULLUP) ? 1 : 0);
			break;

		case PIO_INPUT:
#ifndef __FPGA
			PMC_EnablePeripheral(list->id);
#endif
			PIO_SetInput(list->pio,
						 list->mask,
						 list->attribute);
			break;

		case PIO_OUTPUT_0:
		case PIO_OUTPUT_1:
			PIO_SetOutput(list->pio,
						  list->mask,
						  (list->type == PIO_OUTPUT_1),
						  (list->attribute & PIO_OPENDRAIN) ? 1 : 0,
						  (list->attribute & PIO_PULLUP) ? 1 : 0);
			break;

		default: return 0;
		}

		list++;
		size--;
	}

	return 1;
}

/**
 * \brief Sets a high output level on all the PIOs defined in the given Pin
 * instance.
 * This has no immediate effects on PIOs that are not output, but the PIO
 * controller will memorize the value they are changed to outputs.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 */
void PIO_Set(const Pin *pin)
{
	pin->pio->PIO_SODR = pin->mask;
}

/**
 * \brief Sets a low output level on all the PIOs defined in the given Pin
 * instance.
 * This has no immediate effects on PIOs that are not output, but the PIO
 * controller will memorize the value they are changed to outputs.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 */
void PIO_Clear(const Pin *pin)
{
	pin->pio->PIO_CODR = pin->mask;
}

/**
 * \brief Returns 1 if one or more PIO of the given Pin instance currently have
 * a high level; otherwise returns 0. This method returns the actual value that
 * is being read on the pin. To return the supposed output value of a pin, use
 * PIO_GetOutputDataStatus() instead.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 *
 * \return 1 if the Pin instance contains at least one PIO that currently has
 * a high level; otherwise 0.
 */
unsigned char PIO_Get(const Pin *pin)
{
	unsigned int reg;

	if ((pin->type == PIO_OUTPUT_0) || (pin->type == PIO_OUTPUT_1))
		reg = pin->pio->PIO_ODSR;
	else
		reg = pin->pio->PIO_PDSR;

	if ((reg & pin->mask) == 0)
		return 0;
	else
		return 1;
}

/**
 * \brief Returns 1 if one or more PIO of the given Pin are configured to output
 * a high level (even if they are not output).
 * To get the actual value of the pin, use PIO_Get() instead.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 *
 * \return 1 if the Pin instance contains at least one PIO that is configured
 * to output a high level; otherwise 0.
 */
unsigned char PIO_GetOutputDataStatus(const Pin *pin)
{
	if ((pin->pio->PIO_ODSR & pin->mask) == 0)
		return 0;
	else
		return 1;
}

/**
 * \brief Configures Glitch or Denouncing filter for input.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 * \param cuttoff  Cut off frequency for denounce filter.
 */
void PIO_SetDebounceFilter(const Pin *pin, uint32_t cuttoff)
{
	Pio *pio = pin->pio;

	pio->PIO_IFSCER = pin->mask; /* set Denouncing, 0 bit field no effect */
	pio->PIO_SCDR = ((32678 / (2 * (cuttoff))) - 1) & 0x3FFF;
	/* the lowest 14 bits work */
}

/**
 * \brief Enable write protect.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 */
void PIO_EnableWriteProtect(const Pin *pin)
{
	Pio *pio = pin->pio;

	pio->PIO_WPMR = (PIO_WPMR_WPKEY_VALID | PIO_WPMR_WPEN_EN);
}

/**
 * \brief Disable write protect.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 */

void PIO_DisableWriteProtect(const Pin *pin)
{
	Pio *pio = pin->pio;

	pio->PIO_WPMR = (PIO_WPMR_WPKEY_VALID | PIO_WPMR_WPEN_DIS);
}

/**
 * \brief Get write protect violation information.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 */
uint32_t PIO_GetWriteProtectViolationInfo(const Pin *pin)
{
	Pio *pio = pin->pio;
	return (pio->PIO_WPSR);
}

/**
 *  \brief Set pin type
 * the pin is controlled by the corresponding peripheral (A, B, C, D,E)
 * \param pin      Pointer to a Pin instance describing one or more pins.
 * \param pinType  PIO_PERIPH_A, PIO_PERIPH_B, ...
 */
void PIO_SetPinType(Pin *pin, uint8_t pinType)
{
	pin->type = pinType;
}

/**
 * \brief Set the drive strength of the pin.
 *
 * \param pin  Pointer to a Pin instance describing one or more pins.
 * \param strength  0 for low drive strength or 1 for high drive strength.
 */
void PIO_SetDriveStrength(const Pin *pin, uint8_t strength)
{
	Pio *pio = pin->pio;

	if (strength) {
		pio->PIO_DRIVER |= pin->mask;
	} else {
		pio->PIO_DRIVER &= ~(pin->mask);
	}
}
